﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using NLite.Data.Driver;


namespace NLite.Data
{
    public partial class Projection
    {
        public static IColumnNode Column(string columnNameOrExpression, string tableName)
        {
            return new ColumnNode(columnNameOrExpression, tableName);
        }

        public static IColumnNode Column(string columnNameOrExpression)
        {
            return Column(columnNameOrExpression, null);
        }

        public static IProjectionNode Native(string expression)
        {
            return new NativeNode(expression);
        }
    }

    public partial class Criterion
    {
        public static CriterionNode Native(string expression)
        {
            return new NativeNode(expression);
        }
    }


    public static class Order
    {
        public static OrderItemNode Asc(this IColumnNode col)
        {
            return new OrderItemNode(col, OrderDirection.Asc);
        }

        public static OrderItemNode Desc(this IColumnNode col)
        {
            return new OrderItemNode(col, OrderDirection.Desc);
        }

        
    }

    public partial class Node
    {
        protected class NodeCollection<T> : IList<T>, IEquatable<NodeCollection<T>>, ICloneable where T : class,ICloneable
        {
            private List<T> InnerList = new List<T>();

            public int IndexOf(T item)
            {
                return InnerList.IndexOf(item);
            }

            public void Insert(int index, T item)
            {
                if (InnerList.Contains(item))
                    InnerList.Remove(item);
                InnerList.Insert(index, item);
            }

            public void RemoveAt(int index)
            {
                InnerList.RemoveAt(index);
            }

            public T this[int index]
            {
                get
                {
                    return InnerList[index];
                }
                set
                {
                    Add(value);
                }
            }

            public void Add(T item)
            {
                if (!InnerList.Contains(item)) InnerList.Add(item);

            }

            public void Clear()
            {
                InnerList.Clear();
            }

            public bool Contains(T item)
            {
                return InnerList.Contains(item);
            }

            public void CopyTo(T[] array, int arrayIndex)
            {
                InnerList.CopyTo(array, arrayIndex);
            }

            public int Count
            {
                get { return InnerList.Count; }
            }

            public bool IsReadOnly
            {
                get { return (InnerList as IList<T>).IsReadOnly; }
            }

            public bool Remove(T item)
            {
                return InnerList.Remove(item);
            }

            public IEnumerator<T> GetEnumerator()
            {
                return InnerList.GetEnumerator();
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return InnerList.GetEnumerator();
            }

            public bool Equals(NodeCollection<T> other)
            {
                if (other.InnerList.Count != InnerList.Count) return false;
                var n = InnerList.Count;
                for (int i = 0; i < n; i++)
                    if (!object.Equals(InnerList[i], other.InnerList[i])) return false;
                return true;
            }

            public override bool Equals(object obj)
            {
                return Equals(obj as NodeCollection<T>);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }

            object ICloneable.Clone()
            {
                return Clone();
            }

            public NodeCollection<T> Clone()
            {
                var o = new NodeCollection<T>();
                foreach (var item in InnerList)
                    o.InnerList.Add(item.Clone() as T);
                return o;
            }
        }
    }
   
  
    #region Clone
    public partial class ColumnNode
    {
        public override object Clone()
        {
            return new ColumnNode(Identity.Name, Table)
            {
                Expression = this.Expression == null ? null : this.Expression.Clone() as INode,
            };
        }
    }
    public partial class SqlObject
    {
        public override object Clone()
        {
            return new SqlObject()
            {
                Expression = this.Expression == null ? null : this.Expression.Clone() as INode,
            };
        }
    }
   
    public partial class DeleteNode
    {
        public override object Clone()
        {
            return new DeleteNode(Name)
            {
                Where = this.Where.Clone() as WhereNode,
            };
        }
    }
   
    public partial class GroupNode
    {
        public override object Clone()
        {
            return new GroupNode { InnerItems = this.InnerItems.Clone() };
        }
    }
    public partial class HavingNode
    {
        public override object Clone()
        {
            return new HavingNode
            {
                Criterion = this.Criterion == null ? null : this.Criterion.Clone() as LogicNode
            };
        }
    }
    public partial class InsertNode
    {
        public override object Clone()
        {
            return new InsertNode(Name)
            {
                InnerNameValues = this.InnerNameValues.Clone(),
            };
        }
    }
    public partial class JoinNode
    {
        public override object Clone()
        {
            return new JoinNode(Type, Name, Alias, Condition.Clone() as ICriterionNode);
        }
    }
    public partial class NameValue
    {
        public object Clone()
        {
            return new NameValue(Name, Value);
        }
    }
    public partial class OrderNode
    {
        public override object Clone()
        {
            return new OrderNode
            {
                InnerItems = this.InnerItems.Clone()
            };
        }
    }
    public partial class OrderItemNode
    {
        public override object Clone()
        {
            return new OrderItemNode(Column.Clone() as IProjectionNode, Direction);

        }
    }
    public partial class SelectNode
    {
        public override object Clone()
        {
            return new SelectNode(Name, Alias)
            {
                IsDistinct = this.IsDistinct,
                PageSize = this.PageSize,
                PageIndex = this.PageIndex,
                InnerProjections = this.InnerProjections.Clone(),
                Into = this.Into != null? this.Into.Clone() as SelectIntoNode : null,
                InnerJoins = this.InnerJoins.Clone(),
                InnerCollections = this.InnerCollections.Clone(),
                Where = (WhereNode)this.Where.Clone(),
                Group = (GroupNode)this.Group.Clone(),
                Having = (HavingNode)this.Having.Clone(),
                OrderBy = (OrderNode)this.OrderBy.Clone(),
            };
        }
    }
    public partial class TableNode
    {
        public override object Clone()
        {
            return new TableNode(this.Name, this.Alias);
        }
    }
    public partial class UpdateNode
    {
        public override object Clone()
        {
            return new UpdateNode(Name)
            {
                InnerNameValues = this.InnerNameValues.Clone(),
                Where = this.Where.Clone() as WhereNode
            };
        }
    }
    public partial class WhereNode
    {
        public override object Clone()
        {
            return new WhereNode
            {
                Criterion = this.Criterion == null ? null : this.Criterion.Clone() as LogicNode
            };
        }
    }
    public partial class NativeNode
    {
        public override object Clone()
        {
            NativeNode cloned = new NativeNode(this.Expression);
            return cloned;
        }
    }
    public partial class CompareNode
    {
        public override object Clone()
        {
            return new CompareNode(this.Left == null ? null : this.Left.Clone() as INode
                , this.Operator
                , this.Right == null ? null : this.Right.Clone() as INode);
        }
    }
    public partial class AndNode
    {
        public override object Clone()
        {
            return new AndNode(this.Left == null ? null : this.Left.Clone() as ICriterionNode
                , this.Right == null ? null : this.Right.Clone() as ICriterionNode);
        }
    }
    public partial class OrExpression
    {
        public override object Clone()
        {
            return new OrExpression(this.Left == null ? null : this.Left.Clone() as ICriterionNode
                , this.Right == null ? null : this.Right.Clone() as ICriterionNode);
        }
    }
    public partial class NotNode
    {
        public override object Clone()
        {
            return new NotNode(this.Expression == null ? null : this.Expression.Clone() as ICriterionNode);
        }
    }
    public partial class ExistsNode
    {
        public override object Clone()
        {
            return new ExistsNode(this.Query == null ? null : this.Query.Clone() as SelectNode);
        }
    }
    //public partial class ParameterExpression
    //{
    //    public override object Clone()
    //    {
    //        return new ParameterExpression(Name, Value, Type) { Size = this.Size };
    //    }
    //}
    public partial class IsNullNode
    {
        public override object Clone()
        {
            return new IsNullNode(Value.Clone() as INode);
        }
    }

    public partial class IsNullOrEmptyNode
    {
        public override object Clone()
        {
            return new IsNullOrEmptyNode(Value.Clone() as INode);
        }
    }
    public partial class SubQuery
    {
        public override object Clone()
        {
            return this;
        }
    }

    public partial class BetweenNode
    {
        public override object Clone()
        {
            return new BetweenNode(Column, Left.Clone() as INode, Right.Clone() as INode);
        }
    }

    public partial class LikeNode
    {
        public override object Clone()
        {
            return new LikeNode(Column.Clone() as INode, Value.Clone() as INode);
        }
    }
    
    #endregion

    #region Compare
    public class DbExpressionComparer
    {
        public virtual bool Compare(INode a, INode b)
        {
            if (object.ReferenceEquals(a, b))
                return true;
            if (a == null || b == null)
                return false;
            if (a.NodeType != b.NodeType)
                return false;
            switch (a.NodeType)
            {
                case NodeType.Compare: return CompareBinary(a as CompareNode, b as CompareNode);
                case NodeType.Column: return CompareColumn(a as IColumnNode, b as IColumnNode);
                case NodeType.Delete: return CompareDelete(a as DeleteNode, b as DeleteNode);
                case NodeType.Native: return CompareFilter(a as NativeNode, b as NativeNode);
                case NodeType.And: return CompareAnd(a as AndNode, b as AndNode);
                case NodeType.Or: return CompareOr(a as OrExpression, b as OrExpression);
                case NodeType.Not: return CompareNot(a as NotNode, b as NotNode);
                case NodeType.Exists: return CompareExists(a as ExistsNode, b as ExistsNode);
                case NodeType.Group: return CompareGroupBy(a as GroupNode, b as GroupNode);
                case NodeType.Having: return Compare((a as HavingNode).Criterion, (b as HavingNode).Criterion);
                case NodeType.Insert: return CompareInsert(a as InsertNode, b as InsertNode);
                case NodeType.Join: return CompareJoin(a as JoinNode, b as JoinNode);
                case NodeType.Order: return CompareOrderBy(a as OrderNode, b as OrderNode);
                case NodeType.OrderItem: return CompareOrderItem(a as OrderItemNode, b as OrderItemNode);
                case NodeType.Select: return CompareSelect(a as SelectNode, b as SelectNode);
                case NodeType.Table: return CompareTable(a as TableNode, b as TableNode);
                case NodeType.Update: return CompareUpdate(a as UpdateNode, b as UpdateNode);
                case NodeType.Where: return Compare((a as WhereNode).Criterion, (b as WhereNode).Criterion);

                case NodeType.Parameter: return CompareParameter((a as IParameterNode), (b as IParameterNode));
                case NodeType.IsNull: return true;

                case NodeType.Arithmetic: return CompareArithmetic((a as ArithmeticNode), (b as ArithmeticNode));
                case NodeType.Aggregation: return CompareAggregation(a as AggregationNode, b as AggregationNode);
                case NodeType.SubQuery: return CompareSubQuery(a as SubQuery, b as SubQuery);
                case NodeType.Between: return CompareBetween(a as BetweenNode, b as BetweenNode);
                case NodeType.Like: return CompareLike(a as LikeNode, b as LikeNode);
                case NodeType.Count: return CompareCount(a as CountNode, b as CountNode);
                case NodeType.Alias: return CompareAlias(a as AliasNode, b as AliasNode);
                case NodeType.SqlObject: return ComparePrimitive(a as SqlObject, b as SqlObject);
                case NodeType.Collection: return CompareCollection(a as CollectionNode, b as CollectionNode);
                case NodeType.SelectInto: return CompareSelectInto(a as SelectIntoNode, b as SelectIntoNode);
                case NodeType.Custom: return CompareCustom(a, b);
            }

            return true;
        }

        protected virtual bool CompareSelectInto(SelectIntoNode a, SelectIntoNode b)
        {
            return string.Equals(a.Table,b.Table, StringComparison.InvariantCultureIgnoreCase);
        }

        protected virtual bool CompareCollection(CollectionNode a, CollectionNode b)
        {
            if (a.Type != b.Type) return false;
            return CompareSelect(a.Query, b.Query);
        }


        protected virtual bool ComparePrimitive(SqlObject a, SqlObject b)
        {
            return Compare(a.Expression, b.Expression);
        }

        protected virtual bool CompareAlias(AliasNode a, AliasNode b)
        {
            if (!Compare(a.Projection, b.Projection)) return false;
            return string.Equals(a.Alias,b.Alias);
        }

        protected virtual bool CompareCount(CountNode a, CountNode b)
        {
            if (!Compare(a.Column, b.Column)) return false;
            return Compare(a.Criterion, b.Criterion);
        }

        protected virtual bool CompareLike(LikeNode a, LikeNode b)
        {
            if (!Compare(a.Column, b.Column)) return false;
            return Compare(a.Value, b.Value);
        }

        protected virtual bool CompareBetween(BetweenNode a, BetweenNode b)
        {
            if (!Compare(a.Column, b.Column)) return false;
            if (!Compare(a.Left, b.Left)) return false;
            return Compare(a.Right, b.Right);
        }

        protected virtual bool CompareSubQuery(SubQuery a, SubQuery b)
        {
            if (a.Type != b.Type) return false;
            if (!object.Equals(a.Column, b.Column)) return false;
            if (!Compare(a.Query, b.Query)) return false;
            if (a.Range == null && b.Range != null) return false;
            if (a.Range != null && b.Range == null) return false;
            var rangeA = a.Range.ToArray();
            var rangeB = b.Range.ToArray();
            if (rangeA.Length != rangeB.Length) return false;
            var length = rangeA.Length;

            for (int i = 0; i < length; i++)
                if (!Compare(rangeA[i], rangeB[i])) return false;
            return true;
        }

        protected virtual bool CompareAggregation(AggregationNode a, AggregationNode b)
        {
            if (a.Operator != b.Operator) return false;
            if (!Compare(a.Node, b.Node)) return false;
            return Compare(a.Expression, b.Expression);
        }

        protected virtual bool CompareArithmetic(ArithmeticNode a, ArithmeticNode b)
        {
            if (a.Operator != b.Operator) return false;
            if (!Compare(a.Left, b.Left)) return false;
            return Compare(a.Right, b.Right);
        }
        
        protected bool CompareParameter(IParameterNode a, IParameterNode b)
        {
            if (!object.Equals(a.Value, b.Value)) return false;
            return object.Equals(a.Identity, b.Identity);
        }

        protected virtual bool CompareExists(ExistsNode a, ExistsNode b)
        {
            return CompareSelect(a.Query, b.Query);
        }

        protected virtual bool CompareNot(NotNode a, NotNode b)
        {
            return Compare(a.Expression, b.Expression);
        }

        protected virtual bool CompareOr(OrExpression a, OrExpression b)
        {
            if (!Compare(a.Left, b.Left)) return false;
            return Compare(a.Right, b.Right);
        }

        protected virtual bool CompareAnd(AndNode a, AndNode b)
        {
            if (!Compare(a.Left, b.Left)) return false;
            return Compare(a.Right, b.Right);
        }

        protected virtual bool CompareBinary(CompareNode a, CompareNode b)
        {
            if (a.Operator != b.Operator) return false;
            if (!Compare(a.Left, b.Left)) return false;
            return Compare(a.Right, b.Right);
        }

        protected virtual bool CompareCustom(INode a, INode b)
        {
            return true;
        }

        protected virtual bool CompareUpdate(UpdateNode a, UpdateNode b)
        {
            if (!CompareTable(a, b)) return false;
            return Compare(a.Where.Criterion, b.Where.Criterion);
        }

        protected virtual bool CompareTable(TableNode a, TableNode b)
        {
            if (!string.Equals(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)) return false;
            if (!string.Equals(a.Alias, b.Alias, StringComparison.OrdinalIgnoreCase)) return false;
            return true;
        }

        protected virtual bool CompareSelect(SelectNode a, SelectNode b)
        {
            if (!CompareTable(a, b)) return false;
            if (a.IsDistinct != b.IsDistinct) return false;
            if (a.PageIndex != b.PageIndex) return false;
            if (a.PageSize != b.PageSize) return false;
            if (!string.Equals(a.PrimaryKeyName, b.PrimaryKeyName, StringComparison.OrdinalIgnoreCase)) return false;


            if (!object.Equals(a.Projections, b.Projections)) return false;
            if (!object.Equals(a.Joins, b.Joins)) return false;


            if (!Compare(a.Where.Criterion, b.Where.Criterion)) return false;

            if (!Equals(a.Group, b.Group)) return false;
            if (!Equals(a.Having, b.Having)) return false;
            return Equals(a.OrderBy, b.OrderBy);

        }

        protected virtual bool CompareOrderItem(OrderItemNode a, OrderItemNode b)
        {
            if (a.Direction != b.Direction) return false;
            return Equals(a.Column, b.Column);
        }

        protected virtual bool CompareOrderBy(OrderNode a, OrderNode b)
        {
            return object.Equals(a.Items, b.Items);
        }

        protected virtual bool CompareJoin(JoinNode a, JoinNode b)
        {
            if (a.Type != b.Type) return false;
            if (!CompareTable(a, b)) return false;
            return Compare(a.Condition, b.Condition);
        }

        protected virtual bool CompareInsert(InsertNode a, InsertNode b)
        {
            if (!CompareTable(a, b)) return false;
            return object.Equals(a.NameValues, b.NameValues);
        }

        protected virtual bool CompareGroupBy(GroupNode a, GroupNode b)
        {
            return object.Equals(a.Items, b.Items);
        }

        protected virtual bool CompareFilter(NativeNode a, NativeNode b)
        {
            return string.Equals(a.Expression, b.Expression);
        }

        protected virtual bool CompareDelete(DeleteNode a, DeleteNode b)
        {
            if (!CompareTable(a, b)) return false;
            return Equals(a.Where, b.Where);
        }

        protected virtual bool CompareColumn(IColumnNode a, IColumnNode b)
        {
            if (!object.Equals(a.Identity, b.Identity)) return false;
            if (!string.Equals(a.Table, b.Table, StringComparison.OrdinalIgnoreCase)) return false;
            return Compare(a.Expression, b.Expression);
        }
    }
    #endregion

   
}
